home *** CD-ROM | disk | FTP | other *** search
/ Maximum CD 2009 May / maximum-cd-2009-05.iso / DiscContents / Firefox Setup 3.0.6.exe / nonlocalized / components / storage-Legacy.js < prev    next >
Encoding:
Text File  |  2009-01-19  |  48.8 KB  |  1,406 lines

  1. /* ***** BEGIN LICENSE BLOCK *****
  2.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3.  *
  4.  * The contents of this file are subject to the Mozilla Public License Version
  5.  * 1.1 (the "License"); you may not use this file except in compliance with
  6.  * the License. You may obtain a copy of the License at
  7.  * http://www.mozilla.org/MPL/
  8.  *
  9.  * Software distributed under the License is distributed on an "AS IS" basis,
  10.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11.  * for the specific language governing rights and limitations under the
  12.  * License.
  13.  *
  14.  * The Original Code is mozilla.org code.
  15.  *
  16.  * The Initial Developer of the Original Code is Mozilla Corporation.
  17.  * Portions created by the Initial Developer are Copyright (C) 2007
  18.  * the Initial Developer. All Rights Reserved.
  19.  *
  20.  * Contributor(s):
  21.  *  Justin Dolske <dolske@mozilla.com> (original author)
  22.  *
  23.  * Alternatively, the contents of this file may be used under the terms of
  24.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  25.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  26.  * in which case the provisions of the GPL or the LGPL are applicable instead
  27.  * of those above. If you wish to allow use of your version of this file only
  28.  * under the terms of either the GPL or the LGPL, and not to allow others to
  29.  * use your version of this file under the terms of the MPL, indicate your
  30.  * decision by deleting the provisions above and replace them with the notice
  31.  * and other provisions required by the GPL or the LGPL. If you do not delete
  32.  * the provisions above, a recipient may use your version of this file under
  33.  * the terms of any one of the MPL, the GPL or the LGPL.
  34.  *
  35.  * ***** END LICENSE BLOCK ***** */
  36.  
  37.  
  38. const Cc = Components.classes;
  39. const Ci = Components.interfaces;
  40.  
  41. Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
  42.  
  43. function LoginManagerStorage_legacy() { };
  44.  
  45. LoginManagerStorage_legacy.prototype = {
  46.  
  47.     classDescription  : "LoginManagerStorage_legacy",
  48.     contractID : "@mozilla.org/login-manager/storage/legacy;1",
  49.     classID : Components.ID("{e09e4ca6-276b-4bb4-8b71-0635a3a2a007}"),
  50.     QueryInterface : XPCOMUtils.generateQI([Ci.nsILoginManagerStorage,
  51.                                     Ci.nsILoginManagerIEMigrationHelper]),
  52.  
  53.     __logService : null, // Console logging service, used for debugging.
  54.     get _logService() {
  55.         if (!this.__logService)
  56.             this.__logService = Cc["@mozilla.org/consoleservice;1"].
  57.                                 getService(Ci.nsIConsoleService);
  58.         return this.__logService;
  59.     },
  60.  
  61.     __ioService: null, // IO service for string -> nsIURI conversion
  62.     get _ioService() {
  63.         if (!this.__ioService)
  64.             this.__ioService = Cc["@mozilla.org/network/io-service;1"].
  65.                                getService(Ci.nsIIOService);
  66.         return this.__ioService;
  67.     },
  68.  
  69.     __decoderRing : null,  // nsSecretDecoderRing service
  70.     get _decoderRing() {
  71.         if (!this.__decoderRing)
  72.             this.__decoderRing = Cc["@mozilla.org/security/sdr;1"].
  73.                                  getService(Ci.nsISecretDecoderRing);
  74.         return this.__decoderRing;
  75.     },
  76.  
  77.     __utfConverter : null, // UCS2 <--> UTF8 string conversion
  78.     get _utfConverter() {
  79.         if (!this.__utfConverter) {
  80.             this.__utfConverter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
  81.                                   createInstance(Ci.nsIScriptableUnicodeConverter);
  82.             this.__utfConverter.charset = "UTF-8";
  83.         }
  84.         return this.__utfConverter;
  85.     },
  86.  
  87.     _utfConverterReset : function() {
  88.         this.__utfConverter = null;
  89.     },
  90.  
  91.     __profileDir: null,  // nsIFile for the user's profile dir
  92.     get _profileDir() {
  93.         if (!this.__profileDir) {
  94.             var dirService = Cc["@mozilla.org/file/directory_service;1"].
  95.                              getService(Ci.nsIProperties);
  96.             this.__profileDir = dirService.get("ProfD", Ci.nsIFile);
  97.         }
  98.         return this.__profileDir;
  99.     },
  100.  
  101.     _prefBranch : null,  // Preferences service
  102.  
  103.     _signonsFile : null,  // nsIFile for "signons3.txt" (or whatever pref is)
  104.     _debug       : false, // mirrors signon.debug
  105.  
  106.     /*
  107.      * A list of prefs that have been used to specify the filename for storing
  108.      * logins. (We've used a number over time due to compatibility issues.)
  109.      * This list is also used by _removeOldSignonsFile() to clean up old files.
  110.      */
  111.     _filenamePrefs : ["SignonFileName3", "SignonFileName2", "SignonFileName"],
  112.  
  113.     /*
  114.      * Core datastructures
  115.      *
  116.      * EG: _logins["http://site.com"][0].password
  117.      * EG: _disabledHosts["never.site.com"]
  118.      */
  119.     _logins        : null, 
  120.     _disabledHosts : null,
  121.  
  122.  
  123.     /*
  124.      * log
  125.      *
  126.      * Internal function for logging debug messages to the Error Console.
  127.      */
  128.     log : function (message) {
  129.         if (!this._debug)
  130.             return;
  131.         dump("PwMgr Storage: " + message + "\n");
  132.         this._logService.logStringMessage("PwMgr Storage: " + message);
  133.     },
  134.  
  135.  
  136.  
  137.  
  138.     /* ==================== Public Methods ==================== */
  139.  
  140.  
  141.  
  142.  
  143.     initWithFile : function(aInputFile, aOutputFile) {
  144.         this._signonsFile = aInputFile;
  145.  
  146.         this.init();
  147.  
  148.         if (aOutputFile) {
  149.             this._signonsFile = aOutputFile;
  150.             this._writeFile();
  151.         }
  152.     },
  153.  
  154.     /*
  155.      * init
  156.      *
  157.      * Initialize this storage component and load stored passwords from disk.
  158.      */
  159.     init : function () {
  160.         this._logins  = {};
  161.         this._disabledHosts = {};
  162.  
  163.         // Connect to the correct preferences branch.
  164.         this._prefBranch = Cc["@mozilla.org/preferences-service;1"].
  165.                            getService(Ci.nsIPrefService);
  166.         this._prefBranch = this._prefBranch.getBranch("signon.");
  167.         this._prefBranch.QueryInterface(Ci.nsIPrefBranch2);
  168.  
  169.         this._debug = this._prefBranch.getBoolPref("debug");
  170.  
  171.         // Check to see if the internal PKCS#11 token has been initialized.
  172.         // If not, set a blank password.
  173.         var tokenDB = Cc["@mozilla.org/security/pk11tokendb;1"].
  174.                       getService(Ci.nsIPK11TokenDB);
  175.  
  176.         var token = tokenDB.getInternalKeyToken();
  177.         if (token.needsUserInit) {
  178.             this.log("Initializing key3.db with default blank password.");
  179.             token.initPassword("");
  180.         }
  181.  
  182.         var importFile = null;
  183.         // If initWithFile is calling us, _signonsFile is already set.
  184.         if (!this._signonsFile)
  185.             [this._signonsFile, importFile] = this._getSignonsFile();
  186.  
  187.         // If we have an import file, do a switcharoo before reading it.
  188.         if (importFile) {
  189.             this.log("Importing " + importFile.path);
  190.  
  191.             var tmp = this._signonsFile;
  192.             this._signonsFile = importFile;
  193.         }
  194.  
  195.         // Read in the stored login data.
  196.         this._readFile();
  197.  
  198.         // If we were importing, write back to the normal file.
  199.         if (importFile) {
  200.             this._signonsFile = tmp;
  201.             this._writeFile();
  202.         }
  203.     },
  204.  
  205.  
  206.     /*
  207.      * addLogin
  208.      *
  209.      */
  210.     addLogin : function (login) {
  211.         // Throws if there are bogus values.
  212.         this._checkLoginValues(login);
  213.  
  214.         // We rely on using login.wrappedJSObject. addLogin is the
  215.         // only entry point where we might get a nsLoginInfo object
  216.         // that wasn't created by us (and so might not be a JS
  217.         // implementation being wrapped)
  218.         if (!login.wrappedJSObject) {
  219.             var clone = Cc["@mozilla.org/login-manager/loginInfo;1"].
  220.                         createInstance(Ci.nsILoginInfo);
  221.             clone.init(login.hostname, login.formSubmitURL, login.httpRealm,
  222.                        login.username,      login.password,
  223.                        login.usernameField, login.passwordField);
  224.             login = clone;
  225.         }
  226.  
  227.         var key = login.hostname;
  228.  
  229.         // If first entry for key, create an Array to hold it's logins.
  230.         var rollback;
  231.         if (!this._logins[key]) {
  232.             this._logins[key] = [];
  233.             rollback = null;
  234.         } else {
  235.             rollback = this._logins[key].concat(); // clone array
  236.         }
  237.  
  238.         this._logins[key].push(login);
  239.  
  240.         var ok = this._writeFile();
  241.  
  242.         // If we failed, don't keep the added login in memory.
  243.         if (!ok) {
  244.             if (rollback)
  245.                 this._logins[key] = rollback;
  246.             else
  247.                 delete this._logins[key];
  248.  
  249.             throw "Couldn't write to file, login not added.";
  250.         }
  251.     },
  252.  
  253.  
  254.     /*
  255.      * removeLogin
  256.      *
  257.      */
  258.     removeLogin : function (login) {
  259.         var key = login.hostname;
  260.         var logins = this._logins[key];
  261.  
  262.         if (!logins)
  263.             throw "No logins found for hostname (" + key + ")";
  264.  
  265.         var rollback = this._logins[key].concat(); // clone array
  266.  
  267.         // The specified login isn't encrypted, so we need to ensure
  268.         // the logins we're comparing with are decrypted. We decrypt one entry
  269.         // at a time, lest _decryptLogins return fewer entries and screw up
  270.         // indices between the two.
  271.         for (var i = 0; i < logins.length; i++) {
  272.  
  273.             var [[decryptedLogin], userCanceled] =
  274.                         this._decryptLogins([logins[i]]);
  275.  
  276.             if (userCanceled)
  277.                 throw "User canceled master password entry, login not removed.";
  278.  
  279.             if (!decryptedLogin)
  280.                 continue;
  281.  
  282.             if (decryptedLogin.equals(login)) {
  283.                 logins.splice(i, 1); // delete that login from array.
  284.                 break;
  285.                 // Note that if there are duplicate entries, they'll
  286.                 // have to be deleted one-by-one.
  287.             }
  288.         }
  289.  
  290.         // Did we delete the last login for this host?
  291.         if (logins.length == 0)
  292.             delete this._logins[key];
  293.  
  294.         var ok = this._writeFile();
  295.  
  296.         // If we failed, don't actually remove the login.
  297.         if (!ok) {
  298.             this._logins[key] = rollback;
  299.             throw "Couldn't write to file, login not removed.";
  300.         }
  301.     },
  302.  
  303.  
  304.     /*
  305.      * modifyLogin
  306.      *
  307.      */
  308.     modifyLogin : function (oldLogin, newLogin) {
  309.         // Throws if there are bogus values.
  310.         this._checkLoginValues(newLogin);
  311.  
  312.         this.removeLogin(oldLogin);
  313.         this.addLogin(newLogin);
  314.     },
  315.  
  316.  
  317.     /*
  318.      * getAllLogins
  319.      *
  320.      * Returns an array of nsAccountInfo.
  321.      */
  322.     getAllLogins : function (count) {
  323.         var result = [], userCanceled;
  324.  
  325.         // Each entry is an array -- append the array entries to |result|.
  326.         for each (var hostLogins in this._logins) {
  327.             result = result.concat(hostLogins);
  328.         }
  329.  
  330.         // decrypt entries for caller.
  331.         [result, userCanceled] = this._decryptLogins(result);
  332.  
  333.         count.value = result.length; // needed for XPCOM
  334.         return result;
  335.     },
  336.  
  337.  
  338.     /*
  339.      * removeAllLogins
  340.      *
  341.      * Removes all logins from storage.
  342.      */
  343.     removeAllLogins : function () {
  344.         // Delete any old, unused files.
  345.         this._removeOldSignonsFiles();
  346.  
  347.         // Disabled hosts kept, as one presumably doesn't want to erase those.
  348.         this._logins = {};
  349.         this._writeFile();
  350.     },
  351.  
  352.  
  353.     /*
  354.      * getAllDisabledHosts
  355.      *
  356.      */
  357.     getAllDisabledHosts : function (count) {
  358.         var result = [];
  359.  
  360.         for (var hostname in this._disabledHosts) {
  361.             result.push(hostname);
  362.         }
  363.  
  364.         count.value = result.length; // needed for XPCOM
  365.         return result;
  366.     },
  367.  
  368.  
  369.     /*
  370.      * getLoginSavingEnabled
  371.      *
  372.      */
  373.     getLoginSavingEnabled : function (hostname) {
  374.         return !this._disabledHosts[hostname];
  375.     },
  376.  
  377.  
  378.     /*
  379.      * setLoginSavingEnabled
  380.      *
  381.      */
  382.     setLoginSavingEnabled : function (hostname, enabled) {
  383.         // File format prohibits certain values. Also, nulls
  384.         // won't round-trip with getAllDisabledHosts().
  385.         if (hostname == "." ||
  386.             hostname.indexOf("\r") != -1 ||
  387.             hostname.indexOf("\n") != -1 ||
  388.             hostname.indexOf("\0") != -1)
  389.             throw "Invalid hostname";
  390.  
  391.         if (enabled)
  392.             delete this._disabledHosts[hostname];
  393.         else
  394.             this._disabledHosts[hostname] = true;
  395.  
  396.         this._writeFile();
  397.     },
  398.  
  399.  
  400.     /*
  401.      * findLogins
  402.      *
  403.      */
  404.     findLogins : function (count, hostname, formSubmitURL, httpRealm) {
  405.         var userCanceled;
  406.  
  407.         var logins = this._searchLogins(hostname, formSubmitURL, httpRealm);
  408.  
  409.         // Decrypt entries found for the caller.
  410.         [logins, userCanceled] = this._decryptLogins(logins);
  411.  
  412.         // We want to throw in this case, so that the Login Manager
  413.         // knows to stop processing forms on the page so the user isn't
  414.         // prompted multiple times.
  415.         if (userCanceled)
  416.             throw "User canceled Master Password entry";
  417.  
  418.         count.value = logins.length; // needed for XPCOM
  419.         return logins;
  420.     },
  421.  
  422.     
  423.     /*
  424.      * countLogins
  425.      *
  426.      */
  427.     countLogins : function (aHostname, aFormSubmitURL, aHttpRealm) {
  428.         var logins;
  429.  
  430.         // Normal case: return direct results for the specified host.
  431.         if (aHostname) {
  432.             logins = this._searchLogins(aHostname, aFormSubmitURL, aHttpRealm);
  433.             return logins.length
  434.         } 
  435.  
  436.         // For consistency with how aFormSubmitURL and aHttpRealm work
  437.         if (aHostname == null)
  438.             return 0;
  439.  
  440.         // aHostname == "", so loop through each known host to match with each.
  441.         var count = 0;
  442.         for (var hostname in this._logins) {
  443.             logins = this._searchLogins(hostname, aFormSubmitURL, aHttpRealm);
  444.             count += logins.length;
  445.         }
  446.  
  447.         return count;
  448.     },
  449.  
  450.  
  451.  
  452.  
  453.     /* ==================== Internal Methods ==================== */
  454.  
  455.  
  456.  
  457.  
  458.     /*
  459.      * _searchLogins
  460.      *
  461.      */
  462.     _searchLogins : function (hostname, formSubmitURL, httpRealm) {
  463.         var hostLogins = this._logins[hostname];
  464.         if (hostLogins == null)
  465.             return [];
  466.  
  467.         var result = [], userCanceled;
  468.  
  469.         for each (var login in hostLogins) {
  470.  
  471.             // If search arg is null, skip login unless it doesn't specify a
  472.             // httpRealm (ie, it's also null). If the search arg is an empty
  473.             // string, always match.
  474.             if (httpRealm == null) {
  475.                 if (login.httpRealm != null)
  476.                     continue;
  477.             } else if (httpRealm != "") {
  478.                 // Make sure the realms match. If search arg is null,
  479.                 // only match if login doesn't specify a realm (is null)
  480.                 if (httpRealm != login.httpRealm)
  481.                     continue;
  482.             }
  483.  
  484.             // If search arg is null, skip login unless it doesn't specify a
  485.             // action URL (ie, it's also null). If the search arg is an empty
  486.             // string, always match.
  487.             if (formSubmitURL == null) {
  488.                 if (login.formSubmitURL != null)
  489.                     continue;
  490.             } else if (formSubmitURL != "") {
  491.                 // If the stored login is blank (not null), that means the
  492.                 // login was stored before we started keeping the action
  493.                 // URL, so always match. Unless the search g
  494.                 if (login.formSubmitURL != "" &&
  495.                     formSubmitURL != login.formSubmitURL)
  496.                     continue;
  497.             }
  498.  
  499.             result.push(login);
  500.         }
  501.  
  502.         return result;
  503.     },
  504.  
  505.  
  506.     /*
  507.      * _checkLoginValues
  508.      *
  509.      * Due to the way the signons2.txt file is formatted, we need to make
  510.      * sure certain field values or characters do not cause the file to
  511.      * be parse incorrectly. Reject logins that we can't store correctly.
  512.      */
  513.     _checkLoginValues : function (aLogin) {
  514.         function badCharacterPresent(l, c) {
  515.             return ((l.formSubmitURL && l.formSubmitURL.indexOf(c) != -1) ||
  516.                     (l.httpRealm     && l.httpRealm.indexOf(c)     != -1) ||
  517.                                         l.hostname.indexOf(c)      != -1  ||
  518.                                         l.usernameField.indexOf(c) != -1  ||
  519.                                         l.passwordField.indexOf(c) != -1);
  520.         }
  521.  
  522.         // Nulls are invalid, as they don't round-trip well.
  523.         // Mostly not a formatting problem, although ".\0" can be quirky.
  524.         if (badCharacterPresent(aLogin, "\0"))
  525.             throw "login values can't contain nulls";
  526.  
  527.         // Newlines are invalid for any field stored as plaintext.
  528.         if (badCharacterPresent(aLogin, "\r") ||
  529.             badCharacterPresent(aLogin, "\n"))
  530.             throw "login values can't contain newlines";
  531.  
  532.         // A line with just a "." can have special meaning.
  533.         if (aLogin.usernameField == "." ||
  534.             aLogin.formSubmitURL == ".")
  535.             throw "login values can't be periods";
  536.  
  537.         // A hostname with "\ \(" won't roundtrip.
  538.         // eg host="foo (", realm="bar" --> "foo ( (bar)"
  539.         // vs host="foo", realm=" (bar" --> "foo ( (bar)"
  540.         if (aLogin.hostname.indexOf(" (") != -1)
  541.             throw "bad parens in hostname";
  542.     },
  543.  
  544.  
  545.     /*
  546.      * _getSignonsFile
  547.      *
  548.      * Determines what file to use based on prefs. Returns it as a
  549.      * nsILocalFile, along with a file to import from first (if needed)
  550.      *
  551.      */
  552.     _getSignonsFile : function() {
  553.         var destFile = null, importFile = null;
  554.  
  555.         // We've used a number of prefs over time due to compatibility issues.
  556.         // Use the filename specified in the newest pref, but import from
  557.         // older files if needed.
  558.         for (var i = 0; i < this._filenamePrefs.length; i++) {
  559.             var prefname = this._filenamePrefs[i];
  560.             var filename = this._prefBranch.getCharPref(prefname);
  561.             var file = this._profileDir.clone();
  562.             file.append(filename);
  563.  
  564.             this.log("Checking file " + filename + " (" + prefname + ")");
  565.  
  566.             // First loop through, save the preferred filename.
  567.             if (!destFile)
  568.                 destFile = file;
  569.             else
  570.                 importFile = file;
  571.  
  572.             if (file.exists())
  573.                 return [destFile, importFile];
  574.         }
  575.  
  576.         // If we can't find any existing file, use the preferred file.
  577.         return [destFile, null];
  578.     },
  579.  
  580.  
  581.     /*
  582.      * _removeOldSignonsFiles
  583.      *
  584.      * Deletes any storage files that we're not using any more.
  585.      */
  586.     _removeOldSignonsFiles : function() {
  587.         // We've used a number of prefs over time due to compatibility issues.
  588.         // Skip the first entry (the newest) and delete the others.
  589.         for (var i = 1; i < this._filenamePrefs.length; i++) {
  590.             var prefname = this._filenamePrefs[i];
  591.             var filename = this._prefBranch.getCharPref(prefname);
  592.             var file = this._profileDir.clone();
  593.             file.append(filename);
  594.  
  595.             if (file.exists()) {
  596.                 this.log("Deleting old " + filename + " (" + prefname + ")");
  597.                 try {
  598.                     file.remove(false);
  599.                 } catch (e) {
  600.                     this.log("NOTICE: Couldn't delete " + filename + ": " + e);
  601.                 }
  602.             }
  603.         }
  604.     },
  605.  
  606.  
  607.     /*
  608.      * _upgrade_entry_to_2E
  609.      *
  610.      * Updates the format of an entry from 2D to 2E. Returns an array of
  611.      * logins (1 or 2), as sometimes updating an entry requires creating an
  612.      * extra login.
  613.      */
  614.     _upgrade_entry_to_2E : function (aLogin) {
  615.         var upgradedLogins = [aLogin];
  616.  
  617.         /*
  618.          * For logins stored from HTTP channels
  619.          *    - scheme needs to be derived and prepended
  620.          *    - blank or missing realm becomes same as hostname.
  621.          *
  622.          *  "site.com:80"  --> "http://site.com"
  623.          *  "site.com:443" --> "https://site.com"
  624.          *  "site.com:123" --> Who knows! (So add both)
  625.          *
  626.          * Note: For HTTP logins, the hostname never contained a username
  627.          *       or password. EG "user@site.com:80" shouldn't ever happen.
  628.          *
  629.          * Note: Proxy logins are also stored in this format.
  630.          */
  631.         if (aLogin.hostname.indexOf("://") == -1) {
  632.             var oldHost = aLogin.hostname;
  633.  
  634.             // Check for a trailing port number, EG "site.com:80". If there's
  635.             // no port, it wasn't saved by the browser and is probably some
  636.             // arbitrary string picked by an extension.
  637.             if (!/:\d+$/.test(aLogin.hostname)) {
  638.                 this.log("2E upgrade: no port, skipping " + aLogin.hostname);
  639.                 return upgradedLogins;
  640.             }
  641.  
  642.             // Parse out "host:port".
  643.             try {
  644.                 // Small hack: Need a scheme for nsIURI, so just prepend http.
  645.                 // We'll check for a port == -1 in case nsIURI ever starts
  646.                 // noticing that "http://foo:80" is using the default port.
  647.                 var uri = this._ioService.newURI("http://" + aLogin.hostname,
  648.                                                  null, null);
  649.                 var host = uri.host;
  650.                 var port = uri.port;
  651.             } catch (e) {
  652.                 this.log("2E upgrade: Can't parse hostname " + aLogin.hostname);
  653.                 return upgradedLogins;
  654.             }
  655.  
  656.             if (port == 80 || port == -1)
  657.                 aLogin.hostname = "http://" + host;
  658.             else if (port == 443)
  659.                 aLogin.hostname = "https://" + host;
  660.             else {
  661.                 // Not a standard port! Could be either http or https!
  662.                 // (Or maybe it's a proxy login!) To try and avoid
  663.                 // breaking logins, we'll add *both* http and https
  664.                 // versions.
  665.                 this.log("2E upgrade: Cloning login for " + aLogin.hostname);
  666.  
  667.                 aLogin.hostname = "http://" + host + ":" + port;
  668.  
  669.                 var extraLogin = Cc["@mozilla.org/login-manager/loginInfo;1"].
  670.                                  createInstance(Ci.nsILoginInfo);
  671.                 extraLogin.init("https://" + host + ":" + port,
  672.                                 null, aLogin.httpRealm,
  673.                                 aLogin.username, aLogin.password, "", "");
  674.                 // We don't have decrypted values, unless we're importing from IE,
  675.                 // so clone the encrypted bits into the new entry.
  676.                 extraLogin.wrappedJSObject.encryptedPassword = 
  677.                     aLogin.wrappedJSObject.encryptedPassword;
  678.                 extraLogin.wrappedJSObject.encryptedUsername = 
  679.                     aLogin.wrappedJSObject.encryptedUsername;
  680.  
  681.                 if (extraLogin.httpRealm == "")
  682.                     extraLogin.httpRealm = extraLogin.hostname;
  683.                 
  684.                 upgradedLogins.push(extraLogin);
  685.             }
  686.  
  687.             // If the server didn't send a realm (or it was blank), we
  688.             // previously didn't store anything.
  689.             if (aLogin.httpRealm == "")
  690.                 aLogin.httpRealm = aLogin.hostname;
  691.  
  692.             this.log("2E upgrade: " + oldHost + " ---> " + aLogin.hostname);
  693.  
  694.             return upgradedLogins;
  695.         }
  696.  
  697.  
  698.         /*
  699.          * For form logins and non-HTTP channel logins (both were stored in
  700.          * the same format):
  701.          *
  702.          * Standardize URLs (.hostname and .actionURL)
  703.          *    - remove default port numbers, if specified
  704.          *      "http://site.com:80"  --> "http://site.com"
  705.          *    - remove usernames from URL (may move into aLogin.username)
  706.          *      "ftp://user@site.com" --> "ftp://site.com"
  707.          *
  708.          * Note: Passwords in the URL ("foo://user:pass@site.com") were not
  709.          *       stored in FF2, so no need to try to move the value into
  710.          *       aLogin.password.
  711.          */
  712.  
  713.         // closures in cleanupURL
  714.         var ioService = this._ioService;
  715.         var log = this.log;
  716.  
  717.         function cleanupURL(aURL, allowJS) {
  718.             var newURL, username = null, pathname = "";
  719.  
  720.             try {
  721.                 var uri = ioService.newURI(aURL, null, null);
  722.                 var scheme = uri.scheme;
  723.  
  724.                 if (allowJS && scheme == "javascript")
  725.                     return ["javascript:", null, ""];
  726.  
  727.                 newURL = scheme + "://" + uri.host;
  728.  
  729.                 // If the URL explicitly specified a port, only include it when
  730.                 // it's not the default. (We never want "http://foo.com:80")
  731.                 port = uri.port;
  732.                 if (port != -1) {
  733.                     var handler = ioService.getProtocolHandler(scheme);
  734.                     if (port != handler.defaultPort)
  735.                         newURL += ":" + port;
  736.                 }
  737.  
  738.                 // Could be a channel login with a username. 
  739.                 if (scheme != "http" && scheme != "https" && uri.username)
  740.                     username = uri.username;
  741.  
  742.                 if (uri.path != "/")
  743.                     pathname = uri.path;
  744.  
  745.             } catch (e) {
  746.                 log("Can't cleanup URL: " + aURL + " e: " + e);
  747.                 newURL = aURL;
  748.             }
  749.  
  750.             if (newURL != aURL)
  751.                 log("2E upgrade: " + aURL + " ---> " + newURL);
  752.  
  753.             return [newURL, username, pathname];
  754.         }
  755.  
  756.         const isMailNews = /^(ldaps?|smtp|imap|news|mailbox):\/\//;
  757.  
  758.         // Old mailnews logins were protocol logins with a username/password
  759.         // field name set.
  760.         var isFormLogin = (aLogin.formSubmitURL ||
  761.                            aLogin.usernameField ||
  762.                            aLogin.passwordField) &&
  763.                           !isMailNews.test(aLogin.hostname);
  764.  
  765.         var [hostname, username, pathname] = cleanupURL(aLogin.hostname);
  766.         aLogin.hostname = hostname;
  767.  
  768.         // If a non-HTTP URL contained a username, it wasn't stored in the
  769.         // encrypted username field (which contains an encrypted empty value)
  770.         // (Don't do this if it's a form login, though.)
  771.         if (username && !isFormLogin) {
  772.             var [encUsername, userCanceled] = this._encrypt(username);
  773.             if (!userCanceled)
  774.                 aLogin.wrappedJSObject.encryptedUsername = encUsername;
  775.         }
  776.  
  777.  
  778.         if (aLogin.formSubmitURL) {
  779.             [hostname, username, pathname] = cleanupURL(aLogin.formSubmitURL,
  780.                                                         true);
  781.             aLogin.formSubmitURL = hostname;
  782.             // username, if any, ignored.
  783.         }
  784.  
  785.  
  786.         /*
  787.          * For logins stored from non-HTTP channels
  788.          *    - Set httpRealm so they don't look like form logins
  789.          *     "ftp://site.com" --> "ftp://site.com (ftp://site.com)"
  790.          *
  791.          * Tricky: Form logins and non-HTTP channel logins are stored in the
  792.          * same format, and we don't want to add a realm to a form login.
  793.          * Form logins have field names, so only update the realm if there are
  794.          * no field names set. [Any login with a http[s]:// hostname is always
  795.          * a form login, so explicitly ignore those just to be safe.]
  796.          */
  797.         const isHTTP = /^https?:\/\//;
  798.         const isLDAP = /^ldaps?:\/\//;
  799.         if (!isHTTP.test(aLogin.hostname) && !isFormLogin) {
  800.             // LDAP logins need to keep the path.
  801.             if (isLDAP.test(aLogin.hostname))
  802.                 aLogin.httpRealm = aLogin.hostname + pathname;
  803.             else
  804.                 aLogin.httpRealm = aLogin.hostname;
  805.  
  806.             aLogin.formSubmitURL = null;
  807.  
  808.             // Null out the form items because mailnews will no longer treat
  809.             // or expect these as form logins
  810.             if (isMailNews.test(aLogin.hostname)) {
  811.                 aLogin.usernameField = "";
  812.                 aLogin.passwordField = "";
  813.             }
  814.  
  815.             this.log("2E upgrade: set empty realm to " + aLogin.httpRealm);
  816.         }
  817.  
  818.         return upgradedLogins;
  819.     },
  820.  
  821.  
  822.     /*
  823.      * _readFile
  824.      *
  825.      */
  826.     _readFile : function () {
  827.         var formatVersion;
  828.  
  829.         this.log("Reading passwords from " + this._signonsFile.path);
  830.  
  831.         // If it doesn't exist, just create an empty file and bail out.
  832.         if (!this._signonsFile.exists()) {
  833.             this.log("Creating new signons file...");
  834.             this._writeFile();
  835.             return;
  836.         }
  837.  
  838.         var inputStream = Cc["@mozilla.org/network/file-input-stream;1"].
  839.                           createInstance(Ci.nsIFileInputStream);
  840.         // init the stream as RD_ONLY, -1 == default permissions.
  841.         inputStream.init(this._signonsFile, 0x01, -1, null);
  842.         var lineStream = inputStream.QueryInterface(Ci.nsILineInputStream);
  843.         var line = { value: "" };
  844.  
  845.         const STATE = { HEADER : 0, REJECT : 1, REALM : 2,
  846.                         USERFIELD : 3, USERVALUE : 4,
  847.                         PASSFIELD : 5, PASSVALUE : 6, ACTIONURL : 7,
  848.                         FILLER : 8 };
  849.         var parseState = STATE.HEADER;
  850.  
  851.         var nsLoginInfo = new Components.Constructor(
  852.                 "@mozilla.org/login-manager/loginInfo;1", Ci.nsILoginInfo);
  853.         var processEntry = false;
  854.  
  855.         do {
  856.             var hasMore = lineStream.readLine(line);
  857.             try {
  858.               line.value = this._utfConverter.ConvertToUnicode(line.value);
  859.             } catch (e) {
  860.               this.log("Bad UTF8 conversion: " + line.value);
  861.               this._utfConverterReset();
  862.             }
  863.  
  864.             switch (parseState) {
  865.                 // Check file header
  866.                 case STATE.HEADER:
  867.                     if (line.value == "#2c") {
  868.                         formatVersion = 0x2c;
  869.                     } else if (line.value == "#2d") {
  870.                         formatVersion = 0x2d;
  871.                     } else if (line.value == "#2e") {
  872.                         formatVersion = 0x2e;
  873.                     } else {
  874.                         this.log("invalid file header (" + line.value + ")");
  875.                         throw "invalid file header in signons file";
  876.                         // We could disable later writing to file, so we
  877.                         // don't clobber whatever it is. ...however, that
  878.                         // would mean corrupt files are not self-healing.
  879.                         return;
  880.                     }
  881.                     parseState++;
  882.                     break;
  883.  
  884.                 // Line is a hostname for which passwords should never be saved.
  885.                 case STATE.REJECT:
  886.                     if (line.value == ".") {
  887.                         parseState++;
  888.                         break;
  889.                     }
  890.  
  891.                     this._disabledHosts[line.value] = true;
  892.  
  893.                     break;
  894.  
  895.                 // Line is a hostname, saved login(s) will follow
  896.                 case STATE.REALM:
  897.                     var hostrealm = line.value;
  898.  
  899.                     // Format is "http://site.com", with "(some realm)"
  900.                     // appended if it's a HTTP-Auth login.
  901.                     const realmFormat = /^(.+?)( \(.*\))?$/;
  902.                     var matches = realmFormat.exec(hostrealm);
  903.                     var hostname, httpRealm;
  904.                     if (matches && matches.length == 3) {
  905.                         hostname  = matches[1];
  906.                         httpRealm = matches[2] ?
  907.                                         matches[2].slice(2, -1) : null;
  908.                     } else {
  909.                         if (hostrealm != "") {
  910.                             // Uhoh. This shouldn't happen, but try to deal.
  911.                             this.log("Error parsing host/realm: " + hostrealm);
  912.                         }
  913.                         hostname = hostrealm;
  914.                         httpRealm = null;
  915.                     }
  916.  
  917.                     parseState++;
  918.                     break;
  919.  
  920.                 // Line is the HTML 'name' attribute for the username field
  921.                 // (or "." to indicate end of hostrealm)
  922.                 case STATE.USERFIELD:
  923.                     if (line.value == ".") {
  924.                         parseState = STATE.REALM;
  925.                         break;
  926.                     }
  927.  
  928.                     var entry = new nsLoginInfo();
  929.                     entry.hostname  = hostname;
  930.                     entry.httpRealm = httpRealm;
  931.  
  932.                     entry.usernameField = line.value;
  933.                     parseState++;
  934.                     break;
  935.  
  936.                 // Line is a username
  937.                 case STATE.USERVALUE:
  938.                     entry.wrappedJSObject.encryptedUsername = line.value;
  939.                     parseState++;
  940.                     break;
  941.  
  942.                 // Line is the HTML 'name' attribute for the password field,
  943.                 // with a leading '*' character
  944.                 case STATE.PASSFIELD:
  945.                     entry.passwordField = line.value.substr(1);
  946.                     parseState++;
  947.                     break;
  948.  
  949.                 // Line is a password
  950.                 case STATE.PASSVALUE:
  951.                     entry.wrappedJSObject.encryptedPassword = line.value;
  952.  
  953.                     // Version 2C doesn't have an ACTIONURL  line, so
  954.                     // process entry now.
  955.                     if (formatVersion < 0x2d)
  956.                         processEntry = true;
  957.  
  958.                     parseState++;
  959.                     break;
  960.  
  961.                 // Line is the action URL
  962.                 case STATE.ACTIONURL:
  963.                     var formSubmitURL = line.value;
  964.                     if (!formSubmitURL && entry.httpRealm != null)
  965.                         entry.formSubmitURL = null;
  966.                     else
  967.                         entry.formSubmitURL = formSubmitURL;
  968.  
  969.                     // Version 2D doesn't have a FILLER line, so
  970.                     // process entry now.
  971.                     if (formatVersion < 0x2e)
  972.                         processEntry = true;
  973.  
  974.                     parseState++;
  975.                     break;
  976.  
  977.                 // Line is unused filler for future use
  978.                 case STATE.FILLER:
  979.                     // Save the line's value (so we can dump it back out when
  980.                     // we save the file next time) for forwards compatability.
  981.                     entry.wrappedJSObject.filler = line.value;
  982.                     processEntry = true;
  983.  
  984.                     parseState++;
  985.                     break;
  986.             }
  987.  
  988.             // If we've read all the lines for the current entry,
  989.             // process it and reset the parse state for the next entry.
  990.             if (processEntry) {
  991.                 if (formatVersion < 0x2d) {
  992.                     // A blank, non-null value is handled as a wildcard.
  993.                     if (entry.httpRealm != null)
  994.                         entry.formSubmitURL = null;
  995.                     else
  996.                         entry.formSubmitURL = "";
  997.                 }
  998.  
  999.                 // Upgrading an entry to 2E can sometimes result in the need
  1000.                 // to create an extra login.
  1001.                 var entries = [entry];
  1002.                 if (formatVersion < 0x2e)
  1003.                     entries = this._upgrade_entry_to_2E(entry);
  1004.  
  1005.  
  1006.                 for each (var e in entries) {
  1007.                     if (!this._logins[e.hostname])
  1008.                         this._logins[e.hostname] = [];
  1009.                     this._logins[e.hostname].push(e);
  1010.                 }
  1011.  
  1012.                 entry = null;
  1013.                 processEntry = false;
  1014.                 parseState = STATE.USERFIELD;
  1015.             }
  1016.         } while (hasMore);
  1017.  
  1018.         lineStream.close();
  1019.  
  1020.         return;
  1021.     },
  1022.  
  1023.  
  1024.     /*
  1025.      * _writeFile
  1026.      *
  1027.      * Returns true if the operation was successfully completed, or false
  1028.      * if there was an error (probably the user refusing to enter a
  1029.      * master password if prompted).
  1030.      */
  1031.     _writeFile : function () {
  1032.         var converter = this._utfConverter;
  1033.         function writeLine(data) {
  1034.             data = converter.ConvertFromUnicode(data);
  1035.             data += converter.Finish();
  1036.             data += "\r\n";
  1037.             outputStream.write(data, data.length);
  1038.         }
  1039.  
  1040.         this.log("Writing passwords to " + this._signonsFile.path);
  1041.  
  1042.         var safeStream = Cc["@mozilla.org/network/safe-file-output-stream;1"].
  1043.                          createInstance(Ci.nsIFileOutputStream);
  1044.         // WR_ONLY|CREAT|TRUNC
  1045.         safeStream.init(this._signonsFile, 0x02 | 0x08 | 0x20, 0600, null);
  1046.  
  1047.         var outputStream = Cc["@mozilla.org/network/buffered-output-stream;1"].
  1048.                            createInstance(Ci.nsIBufferedOutputStream);
  1049.         outputStream.init(safeStream, 8192);
  1050.         outputStream.QueryInterface(Ci.nsISafeOutputStream); // for .finish()
  1051.  
  1052.  
  1053.         // write file version header
  1054.         writeLine("#2e");
  1055.  
  1056.         // write disabled logins list
  1057.         for (var hostname in this._disabledHosts) {
  1058.             writeLine(hostname);
  1059.         }
  1060.  
  1061.         // write end-of-reject-list marker
  1062.         writeLine(".");
  1063.  
  1064.         for (var hostname in this._logins) {
  1065.             function sortByRealm(a,b) {
  1066.                 a = a.httpRealm;
  1067.                 b = b.httpRealm;
  1068.  
  1069.                 if (!a && !b)
  1070.                     return  0;
  1071.  
  1072.                 if (!a || a < b)
  1073.                     return -1;
  1074.  
  1075.                 if (!b || b > a)
  1076.                     return  1;
  1077.  
  1078.                 return 0; // a==b, neither is null
  1079.             }
  1080.  
  1081.             // Sort logins by httpRealm. This allows us to group multiple
  1082.             // logins for the same realm together.
  1083.             this._logins[hostname].sort(sortByRealm);
  1084.  
  1085.  
  1086.             // write each login known for the host
  1087.             var lastRealm = null;
  1088.             var firstEntry = true;
  1089.             var userCanceled = false;
  1090.             for each (var login in this._logins[hostname]) {
  1091.  
  1092.                 // If this login is for a new realm, start a new entry.
  1093.                 if (login.httpRealm != lastRealm || firstEntry) {
  1094.                     // end previous entry, if needed.
  1095.                     if (!firstEntry)
  1096.                         writeLine(".");
  1097.  
  1098.                     var hostrealm = login.hostname;
  1099.                     if (login.httpRealm)
  1100.                         hostrealm += " (" + login.httpRealm + ")";
  1101.  
  1102.                     writeLine(hostrealm);
  1103.                 }
  1104.  
  1105.                 firstEntry = false;
  1106.  
  1107.                 // Get the encrypted value of the username. Newly added
  1108.                 // logins will need the plaintext value encrypted.
  1109.                 var encUsername = login.wrappedJSObject.encryptedUsername;
  1110.                 if (!encUsername) {
  1111.                     [encUsername, userCanceled] = this._encrypt(login.username);
  1112.                     login.wrappedJSObject.encryptedUsername = encUsername;
  1113.                 }
  1114.  
  1115.                 if (userCanceled)
  1116.                     break;
  1117.  
  1118.                 // Get the encrypted value of the password. Newly added
  1119.                 // logins will need the plaintext value encrypted.
  1120.                 var encPassword = login.wrappedJSObject.encryptedPassword;
  1121.                 if (!encPassword) {
  1122.                     [encPassword, userCanceled] = this._encrypt(login.password);
  1123.                     login.wrappedJSObject.encryptedPassword = encPassword;
  1124.                 }
  1125.  
  1126.                 if (userCanceled)
  1127.                     break;
  1128.  
  1129.  
  1130.                 writeLine((login.usernameField ?  login.usernameField : ""));
  1131.                 writeLine(encUsername);
  1132.                 writeLine("*" +
  1133.                     (login.passwordField ?  login.passwordField : ""));
  1134.                 writeLine(encPassword);
  1135.                 writeLine((login.formSubmitURL ? login.formSubmitURL : ""));
  1136.                 if (login.wrappedJSObject.filler)
  1137.                     writeLine(login.wrappedJSObject.filler);
  1138.                 else
  1139.                     writeLine("---");
  1140.  
  1141.                 lastRealm = login.httpRealm;
  1142.             }
  1143.  
  1144.             if (userCanceled) {
  1145.                 this.log("User canceled Master Password, aborting write.");
  1146.                 // .close will cause an abort w/o modifying original file
  1147.                 outputStream.close();
  1148.                 return false;
  1149.             }
  1150.  
  1151.             // write end-of-host marker
  1152.             writeLine(".");
  1153.         }
  1154.  
  1155.         // [if there were no hosts, no end-of-host marker (".") needed]
  1156.  
  1157.         outputStream.finish();
  1158.         return true;
  1159.     },
  1160.  
  1161.  
  1162.     /*
  1163.      * _decryptLogins
  1164.      *
  1165.      * Decrypts username and password fields in the provided array of
  1166.      * logins. This is deferred from the _readFile() code, so that
  1167.      * the user is not prompted for a master password (if set) until
  1168.      * the entries are actually used.
  1169.      *
  1170.      * The entries specified by the array will be decrypted, if possible.
  1171.      * An array of successfully decrypted logins will be returned. The return
  1172.      * value should be given to external callers (since still-encrypted
  1173.      * entries are useless), whereas internal callers generally don't want
  1174.      * to lose unencrypted entries (eg, because the user clicked Cancel
  1175.      * instead of entering their master password)
  1176.      */
  1177.     _decryptLogins : function (logins) {
  1178.         var result = [], userCanceled = false;
  1179.  
  1180.         for each (var login in logins) {
  1181.             var username, password;
  1182.  
  1183.             [username, userCanceled] =
  1184.                 this._decrypt(login.wrappedJSObject.encryptedUsername);
  1185.  
  1186.             if (userCanceled)
  1187.                 break;
  1188.  
  1189.             [password, userCanceled] =
  1190.                 this._decrypt(login.wrappedJSObject.encryptedPassword);
  1191.  
  1192.             // Probably can't hit this case, but for completeness...
  1193.             if (userCanceled)
  1194.                 break;
  1195.  
  1196.             // If decryption failed (corrupt entry?) skip it.
  1197.             // Note that we allow password-only logins, so username con be "".
  1198.             if (username == null || !password)
  1199.                 continue;
  1200.  
  1201.             // We could set the decrypted values on a copy of the object, to
  1202.             // try to prevent the decrypted values from sitting around in
  1203.             // memory if they're not needed. But thanks to GC that's happening
  1204.             // anyway, so meh.
  1205.             login.username = username;
  1206.             login.password = password;
  1207.  
  1208.             // Old mime64-obscured entries need to be reencrypted in the new
  1209.             // format.
  1210.             if (login.wrappedJSObject.encryptedUsername &&
  1211.                 login.wrappedJSObject.encryptedUsername.charAt(0) == '~') {
  1212.                   [username, userCanceled] = this._encrypt(login.username);
  1213.  
  1214.                   if (userCanceled)
  1215.                     break;
  1216.  
  1217.                   login.wrappedJSObject.encryptedUsername = username;
  1218.             }
  1219.  
  1220.             if (login.wrappedJSObject.encryptedPassword &&
  1221.                 login.wrappedJSObject.encryptedPassword.charAt(0) == '~') {
  1222.  
  1223.                   [password, userCanceled] = this._encrypt(login.password);
  1224.  
  1225.                   if (userCanceled)
  1226.                     break;
  1227.  
  1228.                   login.wrappedJSObject.encryptedPassword = password;
  1229.             }
  1230.  
  1231.             result.push(login);
  1232.         }
  1233.  
  1234.         return [result, userCanceled];
  1235.     },
  1236.  
  1237.  
  1238.     /*
  1239.      * _encrypt
  1240.      *
  1241.      * Encrypts the specified string, using the SecretDecoderRing.
  1242.      *
  1243.      * Returns [cipherText, userCanceled] where:
  1244.      *  cipherText   -- the encrypted string, or null if it failed.
  1245.      *  userCanceled -- if the encryption failed, this is true if the
  1246.      *                  user selected Cancel when prompted to enter their
  1247.      *                  Master Password. The caller should bail out, and not
  1248.      *                  not request that more things be encrypted (which 
  1249.      *                  results in prompting the user for a Master Password
  1250.      *                  over and over.)
  1251.      */
  1252.     _encrypt : function (plainText) {
  1253.         var cipherText = null, userCanceled = false;
  1254.  
  1255.         try {
  1256.             var plainOctet = this._utfConverter.ConvertFromUnicode(plainText);
  1257.             plainOctet += this._utfConverter.Finish();
  1258.             cipherText = this._decoderRing.encryptString(plainOctet);
  1259.         } catch (e) {
  1260.             this.log("Failed to encrypt string. (" + e.name + ")");
  1261.             // If the user clicks Cancel, we get NS_ERROR_FAILURE.
  1262.             // (unlike decrypting, which gets NS_ERROR_NOT_AVAILABLE).
  1263.             if (e.result == Components.results.NS_ERROR_FAILURE)
  1264.                 userCanceled = true;
  1265.         }
  1266.  
  1267.         return [cipherText, userCanceled];
  1268.     },
  1269.  
  1270.  
  1271.     /*
  1272.      * _decrypt
  1273.      *
  1274.      * Decrypts the specified string, using the SecretDecoderRing.
  1275.      *
  1276.      * Returns [plainText, userCanceled] where:
  1277.      *  plainText    -- the decrypted string, or null if it failed.
  1278.      *  userCanceled -- if the decryption failed, this is true if the
  1279.      *                  user selected Cancel when prompted to enter their
  1280.      *                  Master Password. The caller should bail out, and not
  1281.      *                  not request that more things be decrypted (which 
  1282.      *                  results in prompting the user for a Master Password
  1283.      *                  over and over.)
  1284.      */
  1285.     _decrypt : function (cipherText) {
  1286.         var plainText = null, userCanceled = false;
  1287.  
  1288.         try {
  1289.             var plainOctet;
  1290.             if (cipherText.charAt(0) == '~') {
  1291.                 // The older file format obscured entries by
  1292.                 // base64-encoding them. These entries are signaled by a
  1293.                 // leading '~' character. 
  1294.                 plainOctet = atob(cipherText.substring(1));
  1295.             } else {
  1296.                 plainOctet = this._decoderRing.decryptString(cipherText);
  1297.             }
  1298.             plainText = this._utfConverter.ConvertToUnicode(plainOctet);
  1299.         } catch (e) {
  1300.             this.log("Failed to decrypt string: " + cipherText +
  1301.                 " (" + e.name + ")");
  1302.  
  1303.             // In the unlikely event the converter threw, reset it.
  1304.             this._utfConverterReset();
  1305.  
  1306.             // If the user clicks Cancel, we get NS_ERROR_NOT_AVAILABLE.
  1307.             // If the cipherText is bad / wrong key, we get NS_ERROR_FAILURE
  1308.             // Wrong passwords are handled by the decoderRing reprompting;
  1309.             // we get no notification.
  1310.             if (e.result == Components.results.NS_ERROR_NOT_AVAILABLE)
  1311.                 userCanceled = true;
  1312.         }
  1313.  
  1314.         return [plainText, userCanceled];
  1315.     },
  1316.  
  1317.  
  1318.  
  1319.  
  1320.     /* ================== nsILoginManagerIEMigratorHelper ================== */
  1321.  
  1322.  
  1323.  
  1324.  
  1325.     _migrationLoginManager : null,
  1326.  
  1327.     /*
  1328.      * migrateAndAddLogin
  1329.      *
  1330.      * Given a login with IE6-formatted fields, migrates it to the new format
  1331.      * and adds it to the login manager.
  1332.      *
  1333.      * Experimentally derived format of IE6 logins, see:
  1334.      *     https://bugzilla.mozilla.org/attachment.cgi?id=319346
  1335.      *
  1336.      * HTTP AUTH:
  1337.      * - hostname is always "example.com:123"
  1338.      *   * "example.com", "http://example.com", "http://example.com:80" all
  1339.      *     end up as just "example.com:80"
  1340.      *   * Entering "example.com:80" in the URL bar isn't recognized as a
  1341.      *     valid URL by IE6.
  1342.      *   * "https://example.com" is saved as "example.com:443"
  1343.      *   * "https://example.com:666" is saved as "example.com:666". Thus, for
  1344.      *     non-standard ports we don't know the right scheme, so create both.
  1345.      *
  1346.      * - an empty or missing "realm" in the WWW-Authenticate reply is stored
  1347.      *   as just an empty string by IE6.
  1348.      *
  1349.      * - IE6 will store logins where one or both (!) of the username/password
  1350.      *   is left blank. We don't support logins without a password, so these
  1351.      *   logins won't be added [addLogin() will throw].
  1352.      *
  1353.      * - IE6 won't recognize a URL with and embedded username/password (eg
  1354.      *   http://user@example.com, http://user:pass@example.com), so these
  1355.      *   shouldn't be encountered.
  1356.      *
  1357.      * - Our migration code doesn't extract non-HTTP logins (eg, FTP). So
  1358.      *   they shouldn't be encountered here. (Verified by saving FTP logins
  1359.      *   in IE and then importing in Firefox.)
  1360.      *
  1361.      *
  1362.      * FORM LOGINS:
  1363.      * - hostname is "http://site.com" or "https://site.com".
  1364.      *   * scheme always included
  1365.      *   * default port not included
  1366.      * - port numbers, even for non-standard posts, are never present!
  1367.      *   unfortunately, this means logins will only work on the default
  1368.      *   port, because we don't know what the original was (or even that
  1369.      *   it wasn't originally stored for the original port).
  1370.      * - Logins are stored without a field name by IE, but we look one up
  1371.      *   in the migrator for the username. The password field name will
  1372.      *   always be empty-string.
  1373.      */
  1374.     migrateAndAddLogin : function (aLogin) {
  1375.         // Initialize outself on the first call
  1376.         if (!this._migrationLoginManager) {
  1377.             // Connect to the correct preferences branch.
  1378.             this._prefBranch = Cc["@mozilla.org/preferences-service;1"].
  1379.                                getService(Ci.nsIPrefService);
  1380.             this._prefBranch = this._prefBranch.getBranch("signon.");
  1381.             this._prefBranch.QueryInterface(Ci.nsIPrefBranch2);
  1382.  
  1383.             this._debug = this._prefBranch.getBoolPref("debug");
  1384.  
  1385.             this._migrationLoginManager = Cc["@mozilla.org/login-manager;1"].
  1386.                                           getService(Ci.nsILoginManager);
  1387.         }
  1388.  
  1389.         this.log("Migrating login for " + aLogin.hostname);
  1390.  
  1391.         // The IE login is in the same format as the old password
  1392.         // manager entries, so just reuse that code.
  1393.         var logins = this._upgrade_entry_to_2E(aLogin);
  1394.  
  1395.         // Add logins via the login manager (and not this.addLogin),
  1396.         // lest an alternative storage module be in use.
  1397.         for each (var login in logins)
  1398.             this._migrationLoginManager.addLogin(login);
  1399.     }
  1400. }; // end of nsLoginManagerStorage_legacy implementation
  1401.  
  1402. var component = [LoginManagerStorage_legacy];
  1403. function NSGetModule(compMgr, fileSpec) {
  1404.     return XPCOMUtils.generateModule(component);
  1405. }
  1406.